export const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB

/**
 * Checks if a mark exists in the editor schema
 * @param markName - The name of the mark to check
 * @param editor - The editor instance
 * @returns boolean indicating if the mark exists in the schema
 */
export const isMarkInSchema = (markName, editor) => {
  if (!editor?.schema) return false
  return editor.schema.spec.marks.get(markName) !== undefined;
}

/**
 * Checks if a node exists in the editor schema
 * @param nodeName - The name of the node to check
 * @param editor - The editor instance
 * @returns boolean indicating if the node exists in the schema
 */
export const isNodeInSchema = (nodeName, editor) => {
  if (!editor?.schema) return false
  return editor.schema.spec.nodes.get(nodeName) !== undefined;
}

/**
 * Gets the active attributes of a specific mark in the current editor selection.
 *
 * @param editor - The Tiptap editor instance.
 * @param markName - The name of the mark to look for (e.g., "highlight", "link").
 * @returns The attributes of the active mark, or `null` if the mark is not active.
 */
export function getActiveMarkAttrs(editor, markName) {
  if (!editor) return null
  const { state } = editor
  const marks = state.storedMarks || state.selection.$from.marks()
  const mark = marks.find((mark) => mark.type.name === markName)

  return mark?.attrs ?? null
}

/**
 * Checks if a node is empty
 */
export function isEmptyNode(node) {
  return !!node && node.content.size === 0
}

/**
 * Utility function to conditionally join class names into a single string.
 * Filters out falsey values like false, undefined, null, and empty strings.
 *
 * @param classes - List of class name strings or falsey values.
 * @returns A single space-separated string of valid class names.
 */
export function cn(...classes) {
  return classes.filter(Boolean).join(" ");
}

/**
 * Safely validates if a position is within the document bounds
 * @param editor The TipTap editor instance
 * @param position The position to validate
 * @returns boolean indicating if the position is safe
 */
export function isPositionSafe(editor, position) {
  if (!editor || !editor.state || !editor.state.doc) {
    return false
  }
  
  try {
    const doc = editor.state.doc
    return position >= 0 && position <= doc.content.size
  } catch (error) {
    console.warn('Error validating position:', error)
    return false
  }
}

/**
 * Safely resolves a position in the document
 * @param editor The TipTap editor instance
 * @param position The position to resolve
 * @returns The resolved position or null if unsafe
 */
export function safeResolvePosition(editor, position) {
  if (!isPositionSafe(editor, position)) {
    return null
  }
  
  try {
    return editor.state.doc.resolve(position)
  } catch (error) {
    console.warn('Error resolving position:', error)
    return null
  }
}

/**
 * Safely gets a node at a specific position
 * @param editor The TipTap editor instance
 * @param position The position to get the node from
 * @returns The node at position or null if unsafe
 */
export function safeNodeAt(editor, position) {
  if (!isPositionSafe(editor, position)) {
    return null
  }
  
  try {
    return editor.state.doc.nodeAt(position)
  } catch (error) {
    console.warn('Error getting node at position:', error)
    return null
  }
}

/**
 * Finds the position and instance of a node in the document
 * @param props Object containing editor, node (optional), and nodePos (optional)
 * @param props.editor The TipTap editor instance
 * @param props.node The node to find (optional if nodePos is provided)
 * @param props.nodePos The position of the node to find (optional if node is provided)
 * @returns An object with the position and node, or null if not found
 */
export function findNodePosition(props) {
  const { editor, node, nodePos } = props

  if (!editor || !editor.state?.doc) return null

  // Zero is valid position
  const hasValidNode = node !== undefined && node !== null
  const hasValidPos = nodePos !== undefined && nodePos !== null

  if (!hasValidNode && !hasValidPos) {
    return null
  }

  if (hasValidPos) {
    // 使用安全的节点获取方法
    const nodeAtPos = safeNodeAt(editor, nodePos)
    if (nodeAtPos) {
      return { pos: nodePos, node: nodeAtPos };
    }
    return null
  }

  // Otherwise search for the node in the document
  let foundPos = -1
  let foundNode = null

  editor.state.doc.descendants((currentNode, pos) => {
    // TODO: Needed?
    // if (currentNode.type && currentNode.type.name === node!.type.name) {
    if (currentNode === node) {
      foundPos = pos
      foundNode = currentNode
      return false
    }
    return true
  })

  return foundPos !== -1 && foundNode !== null
    ? { pos: foundPos, node: foundNode }
    : null
}

/**
 * Handles image upload with progress tracking and abort capability
 * @param file The file to upload
 * @param onProgress Optional callback for tracking upload progress
 * @param abortSignal Optional AbortSignal for cancelling the upload
 * @returns Promise resolving to the URL of the uploaded image
 */
export const handleImageUpload = async (file, onProgress, abortSignal) => {
  // Validate file
  if (!file) {
    throw new Error("No file provided")
  }

  if (file.size > MAX_FILE_SIZE) {
    throw new Error(`File size exceeds maximum allowed (${MAX_FILE_SIZE / (1024 * 1024)}MB)`)
  }

  // For demo/testing: Simulate upload progress
  for (let progress = 0; progress <= 100; progress += 10) {
    if (abortSignal?.aborted) {
      throw new Error("Upload cancelled")
    }
    await new Promise((resolve) => setTimeout(resolve, 500))
    onProgress?.({ progress })
  }

  return convertFileToBase64(file, abortSignal);

  // Uncomment for production use:
  // return convertFileToBase64(file, abortSignal);
}

/**
 * Converts a File to base64 string
 * @param file The file to convert
 * @param abortSignal Optional AbortSignal for cancelling the conversion
 * @returns Promise resolving to the base64 representation of the file
 */
export const convertFileToBase64 = (file, abortSignal) => {
  if (!file) {
    return Promise.reject(new Error("No file provided"));
  }

  return new Promise((resolve, reject) => {
    const reader = new FileReader()

    const abortHandler = () => {
      reader.abort()
      reject(new Error("Upload cancelled"))
    }

    if (abortSignal) {
      abortSignal.addEventListener("abort", abortHandler)
    }

    reader.onloadend = () => {
      if (abortSignal) {
        abortSignal.removeEventListener("abort", abortHandler)
      }

      if (typeof reader.result === "string") {
        resolve(reader.result)
      } else {
        reject(new Error("Failed to convert File to base64"))
      }
    }

    reader.onerror = (error) =>
      reject(new Error(`File reading error: ${error}`))
    reader.readAsDataURL(file)
  });
}
